<!DOCTYPE HTML>
<html>
<head>
<title>tiling</title>
<!--

-->
<meta content="width=device-width; initial-scale=1.0;  maximum-scale=1.0; user-scalable=0;" name="viewport" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<meta http-equiv="X-UA-Compatible" content="chrome=1">
<style>
	body { padding:0px; margin:0px; color: white; background-color: black; }
	input { 
		font-size:16px;
		padding: 0px;
	}
	* {font-family: sans-serif}
	form { padding:0px; margin:0px; }
</style>
<script src="../../lib/canvas.js"></script>
<script>
	var phi;
	var long_d = null;
	var arc_d = null;
	var v = new Object();
	var bars = new Array();
	var grouped_bars = new Array();
	var ints = new Array();
	var verts = new Array();

	var p1_edges = new Array();
	var p1_tiles = new Array();
	var p1_verts = new Array();

	var cartwheels = new Array();

	var p2_edges = new Array();
	var p2_tiles = new Array();
	var p2_verts = new Array();

	var p3_edges = new Array();
	var p3_tiles = new Array();
	var p3_verts = new Array();

	var little_tiles = new Array();
	var tiles = new Array();
	var tiles_ints = new Array();
	var edges = new Array();

	var animation = new Object();
	var task = new Object();

	var opts = new Object();

	function on_resize() {
		canvas.width = window.innerWidth;
		canvas.height = window.innerHeight - 90;
		ctx.globalCompositeOperation = 'lighter';

		set_viewport(0, 0, 60);
	}

	function init() {
		canvas_init();
		phi = Math.sqrt(5) + 1;
		phi /= 2;

		window.onresize = on_resize;
		on_resize();
	}
	
	function get_midpoint(p1, p2) {
		var o = new Object();
		o.x = (p1.x + p2.x) / 2;
		o.y = (p1.y + p2.y) / 2;
		return o;
	}

	function get_polygon_center(p) {
		var a = [];
		for (var i in p) { a.push(p[i]); }
		var o = {x: 0, y:0};
		for (var i in a) {
			o.x += a[i].x / a.length;
			o.y += a[i].y / a.length;
		}
		return o;
	}

	function get_midpoint_r(r, p1, p2) {
		var dx = (p2.x - p1.x);
		var dy = (p2.y - p1.y);
		var d = Math.sqrt(dx * dx + dy * dy);
		var th = Math.atan2(dy, dx);
		var o = new Object();
		o.x = p2.x + r * d * Math.cos(th);
		o.y = p2.y + r * d * Math.sin(th);
		return o;
	}

	function get_midpoint_d(d, p1, p2) {
		var dx = (p2.x - p1.x);
		var dy = (p2.y - p1.y);
		var th = Math.atan2(dy, dx);
		var o = new Object();
		o.x = p1.x + d * Math.cos(th);
		o.y = p1.y + d * Math.sin(th);
		return o;
	}

	function get_intersection(a, b) {
		//log_bar("a", a);
		//log_bar("b", b);
		var p1 = a[0];
		var p2 = a[1];
		var p3 = b[0];
		var p4 = b[1];

		// p1 and p2 are on one line,
		// p3 and p4 are on the other
		
		var d = (
			(p4.y - p3.y) * (p2.x - p1.x) -
			(p4.x - p3.x) * (p2.y - p1.y)
		);
		if (0 == d) { return null; }

		var u = (
			(p4.x - p3.x) * (p1.y - p3.y) -
			(p4.y - p3.y) * (p1.x - p3.x)
		) / d;

		var o = new Object();
		o.x = p1.x + u * (p2.x - p1.x);
		o.y = p1.y + u * (p2.y - p1.y);

		o.k = ('(' + o.x + ',' + o.y + ')');
		//log_bar("intersects", [o]);
		return o;
	}

	function get_distance(p1, p2) {
		var dx = (p2.x - p1.x);
		var dy = (p2.y - p1.y);
		return Math.sqrt(dx * dx + dy * dy);
	}

	function get_regular_polygon_points(sides, x, y, r, theta) {
		var p = new Array();
		var th;
		var i;
		for (i = 0; i < sides; ++i) {
			th = theta + i * 2 * Math.PI / sides;
			p[i] = new Object();
			p[i].x = x + r * Math.cos(th);
			p[i].y = y - r * Math.sin(th);
		}
		return p;
	}

	function fill_polygon(p) {
		ctx.save();
		ctx.beginPath();
		ctx.moveTo(p[0].x, p[0].y);
		for (i = 1; i < p.length; ++i) {
			ctx.lineTo(p[i].x, p[i].y);
		}
		ctx.closePath();
		ctx.fill();
		ctx.restore();
		return p;
	}

	function draw_polygon(p) {
		ctx.save();
		ctx.beginPath();
		ctx.moveTo(p[0].x, p[0].y);
		for (i = 1; i < p.length; ++i) {
			ctx.lineTo(p[i].x, p[i].y);
		}
		ctx.closePath();
		ctx.stroke();
		ctx.restore();
		return p;
	}

	function log_bar(label, b) {
		var o = '';
		o += label;
		for (var i = 0; i < b.length; ++i) {
			o += " (" + b[i].x + "," + b[i].y + ")";
		}
		console_log(o);
	}

	function draw_segments(b) {
		//log_bar("draw", b);
		ctx.save();
		ctx.beginPath();
		ctx.moveTo(b[0].x, b[0].y);
		for (var i = 1; i < b.length; ++i) {
			ctx.lineTo(b[i].x, b[i].y);
			//log_bar("draw", b);
		}
		ctx.stroke();
		ctx.restore();
	}

	function draw_bar(s) {
		var b = find_view_bar(s);
		if (null == b) {return; }
		b = scale_points(b);
		draw_segments(b);
	}

	function find_view_bar(s) {
		var o = new Array();
		o.push(get_intersection(s, v.l));
		o.push(get_intersection(s, v.t));
		o.push(get_intersection(s, v.r));
		o.push(get_intersection(s, v.b));

		// remove all points that are outside the viewport

		var b = new Array();
		var p;
		while (o.length > 0) {
			p = o.shift();
			if (null == p) { continue; }
			if (p.x < v.l[0].x - .00001) { continue; }
			if (p.x > v.r[0].x + .00001) { continue; }
			if (p.y < v.t[0].y - .00001) { continue; }
			if (p.y > v.b[0].y + .00001) { continue; }
			b.push(p);
			if (2 == b.length) {break; }
		}
		//log_bar("scaled", b);
		if (2 == b.length) {return b;}
		return null;
	}

	function scale_points(p) {
		var o = new Array();
		for (var i in p) {
			o[i] = {
				x: p[i].x * v.z + v.cx - v.x * v.z, 
				y: p[i].y * v.z + v.cy - v.y * v.z
			}
		}
		return o;
	}

	function find_parallel_line(p, r) {

		var dx = (p[0].x - p[1].x);
		var dy = (p[0].y - p[1].y);

		var th = Math.atan2(dy, dx) - Math.PI / 2;

		var o = new Array();
		for (var i  in p) {
			o[i] = {x: p[i].x, y: p[i].y};
			o[i].x += r * Math.cos(th);
			o[i].y += r * Math.sin(th);
		}

		return o;
	}

	function find_center_bar(theta) {
		return find_bar(0, 0, theta);
	}

	function find_bar(x, y, theta) {
		return [{x: x, y:y}, {x: x + Math.cos(theta), y: y - Math.sin(theta)}]
	}

	function find_intersection(a, b) {
		var i = [a.k, b.k];
		i.sort();
		var k = i.join(':');
		if (null == ints[k]) { 
			ints[k] = get_intersection(a.bar, b.bar);
			// why are we trying to find intersecting parallell bars?
			if (null == ints[k]) {
				delete ints[k];
				return null;
			}
			ints[k].bars = [a, b];
			ints[k].t = 'int';
			ints[k].k = k;
			ints[k].links = [];
			ints[k].links[a.k] = new Object();
			ints[k].links[b.k] = new Object();
			tiles_ints[k] = [];
			ints[k].tiles = tiles_ints[k];
		}
		return {p: ints[k], k: k, tiles: tiles_ints[k]};
	}

	function find_group_center_bars(g, r, b) {
		var gb = grouped_bars[g];
		var c = Math.floor(gb.length / 2);

		var bar;
		for (var i = c - r; i <= c + r; ++i) {
			if (i < 0) { continue; }
			if (i >= gb.length) { continue; }
			b[gb[i].k] = gb[i];
		}
	}

	function get_center_bars(r) {
		var b = new Array();
		// get r bars in each direction from the center
		for (var i in grouped_bars) {
			find_group_center_bars(i, r, b);
		}
		return b;
	}

	function find_all_intersections() {

		//var cb = get_center_bars(3);
		var cb = bars;
		//draw_bars(cb);
		for (var i in cb) {
			var a = new Array();
			var bar = bars[i];
			for (var j in cb) {
				if (bars[i].g == bars[j].g) { continue; }
				var inter = find_intersection(bars[i], bars[j]);
				inter.bars = [bars[i], bars[j]];
				a.push(inter);
			}
			a.sort(sort_intersections);
			for (var j = 1; j < a.length - 1; ++j) {
				var p = a[j - 1].p;
				var c = a[j].p;
				var n = a[j + 1].p;
				var k = bar.k;
				p.links[k].next = c;
				c.links[k].prev = p;
				c.links[k].next = n;
				n.links[k].prev = c;
			}
			bars[i].ints = a;
		}
	}

	function sort_intersections(a, b) {
		var o = a.p.x - b.p.x;
		if (0 == o) {return a.p.y - b.p.y;}
		return o;
	}

	function animate_tiles() { 
		if (!task.ready) { return; }
		canvas_clear();
		task_draw_tiles();
	}

	function count_tiles(o) { return count_things('tiles', o);}
	function count_roots(o) { return count_things('roots', o);}

	function count_things(k, o) {
		if (null == o) { return 0; }
		if (null == o[k]) { return 0; }
		var n = 0;
		for (var i in o[k]) { ++n; }
		return n;
	}

	function find_common_tiles(a) {
		// find the tiles common to items in a
		var o = new Array();
		var n = 0; // number of items in a.
		for (var i in a) {
			++n;
			for (var t in a[i].tiles) {
				var k = a[i].tiles[t].k;
				if (null == o[k]) { o[k] = 0; }
				++o[k];
			}
		}
		var c = new Array();
		for (var k in o) {
			if (n == o[k]) { c.push(tiles[k]); }
		}
		return c;
	}

	function find_common_bars(a) {
		var o = new Array();
		var b = new Array();
		var len = 0;
		for (var i in a) {
			++len;
			for (var j in a[i].bars) {
				var k = a[i].bars[j].k;
				if (null == b[k]) {b[k] = 0;}
				b[k]++;
			}
		}
		for (var k in b) {
			if (len == b[k]) { o.push(bars[k]); }
		}
		return o;
	}

	function get_theta(b) {
		var dx = b[1].x - b[0].x;
		var dy = b[1].y - b[0].y;
		return Math.atan2(-dy, dx);
	}

	function draw_all_bars(n) {
		draw_bars(bars, n);
	}

	function draw_bars(b, n) {
		for (var k in b) {
			var bar = b[k];
			var clr = rainbow_color(.5 + bar.d / 12);
			ctx.strokeStyle = clr;
			ctx.lineWidth = 1;
			if (null != n) {ctx.lineWidth = n};
			draw_bar(bar.bar);
			ctx.lineWidth = 1.0;
		}
	}

	function clear_globals() {
		bars = new Array();
		grouped_bars = new Array();
		ints = new Array();
		tiles_ints = new Array();

		tiles = new Array();
		edges = new Array();
		verts = new Array();

		p1_verts = new Array();
		p1_edges = new Array();
		p1_tiles = new Array();

		cartwheels = new Array();

		p2_verts = new Array();
		p2_edges = new Array();
		p2_tiles = new Array();

		p3_verts = new Array();
		p3_edges = new Array();
		p3_tiles = new Array();

		//long_d = null;
	}

	function find_all_bars() {
		var a = new Array();
		var d = 1;
		for (var i = 0; i < 5; ++i) {
			theta = (1 + i * 2) * Math.PI / 5;
			a.push(find_bars(theta, d, i));
			d = -d;
		}
		return a;
	}

	function sort_bars(a, b) {
		return a.i - b.i;
	}

	function find_bars(theta, r, ind) {
		// find the ammann bars for the given direction
		// that fit in the viewport
		var c = find_center_bar(theta);
		var e = find_center_bar(theta + Math.PI / 2);
		var o = find_bar(v.x, v.y, theta);
		var b = [{x:0, y:0}, get_intersection(e, o)];
		var d = get_distance(b[0], b[1]);
		// we want the approximate musical sequence term
		// given distance d

		// the formula is:
		// bars = a + b
		// a ~= d / (2 + phi);
		// b ~= a * phi
		// bars = 
		var ta = Math.abs(d / (2 + phi));
		var tb = Math.abs(ta * phi);
		var t = Math.round(ta + tb);


		//t = 0;
		//a = [o, e, c];
		a = [];
		var ti = t;
		var bar;
		var tried = new Array();

		var tries = [
			[t, 1],
			[t, -1],
			[-t, 1],
			[-t, -1]
		];

		for (var i = 0; i < tries.length; ++i) {
		ti = tries[i][0];
		var incr = tries[i][1];
		var md;
		while(true) { 
		if (!tried[ti]) {
			tried[ti] = true;
			md = r * find_musical_distance(ti);
			bar = find_parallel_line(c, md);
			if (null == find_view_bar(bar)) { break; }
			a.push({
			bar: bar, 
			d: md, 
			i: ti, 
			theta: theta,
			g: ind,
			tiles: [],
			k: '[' + ind + ":" + ti + ']'
			});
		} 
		ti += incr; }
		}
		a.sort(sort_bars);

		for (i = 0; i < a.length; ++i) {
			bars[a[i].k] = a[i];
		}

		grouped_bars[ind] = a;

		return a;

	}

	function clear_tasks() {
		task.ready = false;
		if (null != animation.interval) { clearInterval(animation.interval); }
		if (null != task.interval) { clearTimeout(task.interval); }
	}

	function task_find_bars() {
		clearTimeout(task.interval);

		find_all_bars();
		find_all_intersections();
		//draw_all_bars(0.1);
		task.interval = setTimeout('task_find_little_triangles()', 10);
	}

	function find_little_tile_edge(a, b) {
		var k = [a.k, b.k];
		k.sort();
		if (null == task.edges[k]) {
			task.edges[k] = { 
				k: k, 
				triangles: [], 
				ints: [a, b], 
				tiles: [] 
			};
		}

		return task.edges[k];
	}

	function save_task_triangles(a) {
		//find the smallest polygon including the given verts
		if (3 != a.length) { return; }
		var k = [];
		for (var i in a) { 
			if (null == a[i]) { return;} 
			k.push(a[i].k);
		}
		k.sort();
		k = '<' + k.join('+') + '>';
		task.triangles[k] = {k: k, ints: a, edges: []};
		var e;
		e = find_little_tile_edge(a[0], a[1]);
		e.triangles[k] = task.triangles[k];
		task.triangles[k].edges[e.k] = e;
		e = find_little_tile_edge(a[1], a[2]);
		e.triangles[k] = task.triangles[k];
		task.triangles[k].edges[e.k] = e;

		task.triangles[k].hyp = find_little_tile_edge(a[0], a[2]);

		//ctx.fillStyle = "rgb(7, 7, 7)";
		//fill_polygon(scale_points(a));

	}

	function find_little_triangles(inter) {
		//find all the little tiles around the given intersection
		//each pair of edges is unique to a tile

		// there are up to four combinations:
		//	a prev + b prev
		//	a prev + b next
		//	a next + b prev
		//	a next + b next

		var a = [];
		for (var i in inter.links) {
			a.push(inter.links[i]);
		}
		if (a.length != 2) { return; }
		save_task_triangles([a[0].prev, inter, a[1].prev]);
		save_task_triangles([a[0].prev, inter, a[1].next]);
		save_task_triangles([a[0].next, inter, a[1].prev]);
		save_task_triangles([a[0].next, inter, a[1].next]);
	}

	function task_find_next_little_triangles() {

		clearTimeout(task.interval);
		var a = new Array();
		for (var i = 0; i < 256; ++i) {
			if (task.queue.length < 1) { break; }
			a.push(task.queue.shift());
		}
		for (var i in a) { 
			find_little_triangles(a[i]); 
		}
	       	status_update("t" + Math.floor(100 * (task.total - task.queue.length) / task.total) + '%');
		if (a.length > 0) {
			task.interval = setTimeout('task_find_next_little_triangles()', 10);
		} else {
			task.interval = setTimeout('task_find_little_tiles()', 10);
		}
	}

	function task_find_little_triangles() {
		clearTimeout(task.interval);
		task.edges = new Array();
		task.triangles = new Array();
		task.queue = new Array();
		for (var i in ints) {
			task.queue.push(ints[i]);
		}
		task.total = task.queue.length;
		task.interval = setTimeout('task_find_next_little_triangles()', 10);
	}

	/*
	line segment intersection algorithm lifted from here:
	http://www.bryceboe.com/2006/10/23/line-segment-intersection-algorithm/comment-page-1/

	*/

	function points_ccw(a, b, c) {
		// are these points counter clockwise?
		return ((c.y - a.y) * (b.x - a.x)) > ((b.y - a.y) * (c.x - a.x));
	}

	function segments_intersect(a, b) {
		return(
			(
				points_ccw(a[0], b[0], b[1]) != 
				points_ccw(a[1], b[0], b[1])
			) && (
				points_ccw(a[0], a[1], b[0]) != 
				points_ccw(a[0], a[1], b[1])
			)
		);
	}

	function triangles_cross(a, b) {
		// the triangles cross if their hypotenuses cross
		// but they do not cross if their hypotenuses share a vertex
		if (a.hyp.ints[0].k == b.hyp.ints[0].k) { return false; }
		if (a.hyp.ints[1].k == b.hyp.ints[0].k) { return false; }
		if (a.hyp.ints[0].k == b.hyp.ints[1].k) { return false; }
		if (a.hyp.ints[1].k == b.hyp.ints[1].k) { return false; }
		return segments_intersect(a.hyp.ints, b.hyp.ints);
	}

	function find_crossing_triangle(tri, tried) {
		for (var i in tri.edges) {
			var edge = tri.edges[i];
			for (var j in edge.triangles) {
				var t = edge.triangles[j];
				if (tried[t.k]) { continue; }
				if (triangles_cross(tri, t)) {
					return t;
				}
			}
		}
		return null;
	}

	function find_little_tile_edges(t) {
		var p = t.ints[t.ints.length - 1];
		var e;
		for (var i = 0; i < t.ints.length; ++i) {
			e = find_little_tile_edge(p, t.ints[i]);
			e.tiles[t.k] = t;
			t.edges[e.k] = e;
			p = t.ints[i];
		}
	}

	function find_little_tile(poly) {
		var k = [];
		for (var i in poly) { k.push(poly[i].k); }
		k.sort;
		k = '{' + k.join('+') + '}';
		if (null == little_tiles[k]) {
			little_tiles[k] = {
				k: k,
				edges: [],
				ints: poly
			}
		}
		find_little_tile_edges(little_tiles[k]);
		return little_tiles[k];
	}

	function find_little_tiles(tri) {
		if (tri.found) { return; }
		// find all other triangles that cross the hypotenuse
		// then recurse on them until we've found them all
		var a = [];
		var t = tri;
		var tried = [];
		while (true) {
			a.push(t);
			tried[t.k] = true;
			t = find_crossing_triangle(t, tried);
			if (null == t) { break; }
		}
		var p = []
		for (var i in a) {
			a[i].found = true;
			p.push(a[i].ints[1]);
		}
		if (1 == a.length) {
			p.push(a[0].ints[2]);
			p.push(a[0].ints[0]);
		}
		find_little_tile(p);
	}

	function task_find_next_little_tiles() {

		clearTimeout(task.interval);
		var a = new Array();
		for (var i = 0; i < 256; ++i) {
			if (task.queue.length < 1) { break; }
			a.push(task.queue.shift());
		}
		for (var i in a) { 
			find_little_tiles(a[i]); 
		}
	       	status_update("l" + Math.floor(100 * (task.total - task.queue.length) / task.total) + '%');
		if (a.length > 0) {
			task.interval = setTimeout('task_find_next_little_tiles()', 10);
		} else {
			task.interval = setTimeout('task_find_little_tile_types()', 10);
		}
	}

	function task_find_little_tiles() {
		clearTimeout(task.interval);
		little_tiles = new Array();
		task.queue = new Array();
		delete(task.ints);
		for (var i in task.triangles) {
			task.queue.push(task.triangles[i]);
		}
		task.total = task.queue.length;
		task.interval = setTimeout('task_find_next_little_tiles()', 10);
	}


	function find_neighbor_tile(t, edge) {
		for (var i in edge.tiles) {
			var ot = edge.tiles[i];
			if (ot.k != t.k) { return ot; }
		}
		return null;
	}

	function find_little_pentagon_type(t) {
		var a = [];
		var ot;
		for (var i = 0; i < 7; ++i) {a[i] = 0;}
		for (var i in t.edges) {
			var ot = find_neighbor_tile(t, t.edges[i]);
			if (null == ot) { continue; }
			a[ot.ints.length]++;
		}
		if (5 == a[5]) { t.t = 'star'; return}
		if (5 == a[3]) { 
			t.t = 'sun'; 
			mark_kites(t);
			return;
		} else {
			mark_deuces(t);
		}
		t.t = 'tip'; 
	}

	function mark_deuces(t) {
		var ot;
		for (var i in t.edges) {
			var ot = find_neighbor_tile(t, t.edges[i]);
			if (null == ot) { continue; }
			if (3 == ot.ints.length) { 
				if ('kite' != ot.t) {
					ot.t = 'deuce'; 
				}
			}
		}
	}

	function mark_kites(t) {
		var ot;
		for (var i in t.edges) {
			var ot = find_neighbor_tile(t, t.edges[i]);
			if (null == ot) { continue; }
			if (3 == ot.ints.length) { ot.t = 'kite'; }
		}
	}

	function find_little_tile_type(t) {
		// i don't know the official names for the tiles in p1
		// the little tiles made by the ammann bars will help us
		// find them.
		if (7 == t.ints.length) {
			t.t = 'diamond';
		} else if (6 == t.ints.length) {
			t.t = 'boat';
		} else if (4 == t.ints.length) {
			t.t = 'point';
			mark_kites(t);
		} else if (5 == t.ints.length) {
			find_little_pentagon_type(t);
		}
	}

	function task_find_next_little_tile_types() {

		clearTimeout(task.interval);
		var a = new Array();
		for (var i = 0; i < 256; ++i) {
			if (task.queue.length < 1) { break; }
			a.push(task.queue.shift());
		}
		for (var i in a) { 
			find_little_tile_type(a[i]); 
		}
	       	status_update("p" + Math.floor(100 * (task.total - task.queue.length) / task.total) + '%');
		if (a.length > 0) {
			task.interval = setTimeout('task_find_next_little_tile_types()', 10);
		} else {
			task.interval = setTimeout('task_find_p1_tiles()', 10);
		}
	}

	function task_find_little_tile_types() {
		clearTimeout(task.interval);
		task.queue= new Array();
		for (var i in little_tiles) {
			task.queue.push(little_tiles[i]);
		}
		task.total = task.queue.length;
		task.interval = setTimeout('task_find_next_little_tile_types()', 10);
	}

	function task_find_next_p1_tiles() {

		clearTimeout(task.interval);
		var a = new Array();
		for (var i = 0; i < 128; ++i) {
			if (task.queue.length < 1) { break; }
			a.push(task.queue.shift());
		}
		for (var i in a) { 
			find_p1_tiles(a[i]); 
		}
	       	status_update("1t" + Math.floor(100 * (task.total - task.queue.length) / task.total) + '%');
		if (a.length > 0) {
			task.interval = setTimeout('task_find_next_p1_tiles()', 10);
		} else {
			task.interval = setTimeout('task_find_p3_edges()', 10);
		}
	}

	function find_little_tip_corner(t) {
		var ot;
		var a = [];
		for (var i in t.edges) {
			var ot = find_neighbor_tile(t, t.edges[i]);
			if (null == ot) { continue; }
			if ('kite' == ot.t) {
				a.push(t.edges[i]);
			}
		}
		if (a.length != 2) { return; }

		var o = [];
		var c = null;
		for (var i in a) {
			for (var j in a[i].ints) {
				var vert = a[i].ints[j];
				if (null == o[vert.k]) {
					o[vert.k] = vert;
				} else {
					return vert;
				}
			}
		}
		return null;
	}

	function find_tip_vert(t) {

		if (null != t.vert) { return; }
		var c = find_little_tip_corner(t);
		if (null == c) { return; }

		var ci = null;

		for (var i = 0; i < t.ints.length; ++i) {
			if (c.k == t.ints[i].k) { ci = i; break; }
		}
		if (null == ci) { return; }

		var a = [
			t.ints[(ci + 1) % 5],
			t.ints[(5 + ci - 2) % 5]
		];
		for (var i in a) { if (null == a[i]) { return; } }
		var b = [
			t.ints[(ci + 2) % 5],
			t.ints[(5 + ci - 1) % 5]
		];
		for (var i in b) { if (null == b[i]) { return; } }
		c = get_intersection(a, b);
		if (null == c) { return; }
		t.vert = c;
		t.vert.tile = t;

		/*
		ctx.strokeStyle = 'gray';
		draw_segments(scale_points(a));
		draw_segments(scale_points(b));

		var dot = new_draw_dot();
		dot.clr = "white";
		dot.r = 2;
		var p;
		p = scale_points([t.vert])[0];
		dot.x = p.x;
		dot.y = p.y;
		draw_dot(dot);
		*/
	}

	function get_neighbors_t(t, typ) {
		var o = [];
		for (var i in t.edges) {
			var ot = find_neighbor_tile(t, t.edges[i]);
			if (null == ot) { continue; }
			if (typ == ot.t) { o.push(ot); }
		}
		return o;
	}

	function find_big_pentagon(t) {
		if (null != t.pentagon) {return;}
		// the big pentagon is five verts from the five
		// tip tiles surrounding this tile

		// if this tile is not a star, some of the tip tiles
		// will not directly border this.  they will be on the
		// other side of a kite and a deuce
		var ot;
		var o = [];
		o.tiles = [];
		o.flags = [];
		for (var i in t.edges) {
			ot = find_neighbor_tile(t, t.edges[i]);
			if (null == ot) { return; }
			var flag = false;
			if ('kite' == ot.t) {
				flag = true;
				ot = get_neighbors_t(ot, 'tip');
				if (0 == ot.length) {return;}
				ot = ot[0];
			}
			if ('tip' != ot.t) { continue; }
			if (null == ot.vert) {
				find_tip_vert(ot);
				if (null == ot.vert) { return; }
			}
			o.tiles.push(ot);
			o.flags.push(flag);
		}
		if (5 != o.tiles.length) { return; }
		t.pentagon = o;
	}

	function find_p1_vert(vert) {
		var k = vert.k;
		if (null == p1_verts[k]) {
			p1_verts[k] = {
				k: k, 
				draw: 'p1_vert',
				x: vert.x, 
				y: vert.y,
				tiles: [],
				edges: []
			};
		}
		var o = p1_verts[k];
		return o;
	}

	function find_p1_edge(a, b) {
		var k = [a.k, b.k];
		k.sort();
		k = k.join('x');
		if (null == p1_edges[k]) {
			p1_edges[k] = {
				k: k,
				draw: 'p1_edge',
				verts: [],
				tiles: []
			};
			p1_edges[k].verts[a.k] = a;
			p1_edges[k].verts[b.k] = b;
		}
		return p1_edges[k];
	}

	function find_p1_tile(t) {
		var k = t.k;
		if (null != p1_tiles[k]) { return p1_tiles[k]; }
		var o = {k: k, t: t.t, verts: [], edges: []};
		o.little_tile = t;
		o.color = find_little_tile_color(t);
		t.color = o.color;
		t.p1_tile = o;
		o.draw = 'p1_tile';

		for (var i = 0; i < t.verts.length; ++i) {
			o.verts.push(find_p1_vert(t.verts[i]));
		}

		for (var i = 0; i < o.verts.length; ++i) {
			var a = o.verts[i];
			var b = o.verts[(i + 1) % o.verts.length];
			var e = find_p1_edge(a, b);
			o.edges.push(e);
			a.tiles[k] = o;
			e.tiles[k] = o;
		}
		p1_tiles[k] = o;
		var c = o.little_tile.center;
		if (null == c) { c = get_polygon_center(o.verts); }
		o.vert = {
			k: t.k,
			x: c.x,
			y: c.y,
			tile: o,
			color: o.color,
			t: (
				'star' == o.t ||
				'boat' == o.t ||
				'diamond' == o.t
			)
		};
		if ('boat' == o.t) { find_cartwheel(o); }
		return o;
	}

	function find_cartwheel(t) {
		var k = t.k;
		var o = cartwheels[k];
		if (null == o) {
			cartwheels[k] = {
				k: k, 
				draw: 'cartwheel',
				p1_tile: t
			};
		}
		return o;
	}

	function find_big_pentagon_verts(t) {
		if (null != t.verts) {return;}
		// this tile is part of a big pentagon.
		find_big_pentagon(t);
		if (null == t.pentagon) { return; }
		var a = [];
		for (var i = 0; i < 5; ++i) {
			a.push(t.pentagon.tiles[i].vert);
		}
		if (5 != a.length) { return; }
		t.center = get_polygon_center(a);
		var b = [];
		for (var i = 0; i < 5; ++i) {
			var ba = [a[i], a[(i + 2) % 5]];
			var bb = [ a[(i + 4) % 5], a[(i + 6) % 5] ];
			var c = get_intersection(ba, bb);
			if (null == c) { return; }
			c.tiles = [];
			c.tiles[ba[0].tile.k] = ba[0].tile;
			c.tiles[bb[1].tile.k] = bb[1].tile;
			if (!( t.pentagon.flags[i])) {
				b.push(a[i]);
			}
			if (!( 
				t.pentagon.flags[i] &&
				t.pentagon.flags[(i + 1) % 5]
			) || (
				'diamond' == t.t &&
				! t.pentagon.flags[(i + 3) % 5]
			)) {
				b.push(c);
			}
		}
		t.verts = b;
		find_p1_tile(t);
	}

	function find_common_neighbors(a) {
		var o = [];
		var c = [];
		var n = 0;
		for (var i in a) {
			++n;
			var t = a[i];
			var f = []; // neighbors on more than one edge
			for (var j in t.edges) {
				var ot = find_neighbor_tile(t, t.edges[j]);
				if (null == ot) { continue; }
				if (null == o[ot.k]) { 
					o[ot.k] = ot; 
					c[ot.k] = 0;
				}
				if (null == f[ot.k]) { // only count once
					++c[ot.k];
					f[ot.k] = true;
				}
			}
		}
		for (var i in c) {
			if (c[i] != n) { delete o[i]; }
		}
		return o;
	}

	function find_p1_deuce_verts(t) {
		//two neighbors are tips
		// one neighbor is either a boat or a diamond
		// the tips straddle either a star or a boat

		var tips = [];
		var star = null;
		for (var i in t.edges) {
			var ot = find_neighbor_tile(t, t.edges[i]);
			if (null == ot) { continue; }
			if ('tip' == ot.t) {
				if (null == ot.vert) {
					find_p1_tiles(ot);
				}
				if (null == ot.vert) { return; }
				tips.push(ot);
			} else {
				star = ot;
			}
		}

		if (null == star) { return; }
		if (2 != tips.length) { return; }
		if (null == star.verts) { find_p1_tiles(star); }
		if (null == star.verts) { return; }
		var e = [];
		for (var i in star.verts) {
			var vert = star.verts[i];
			if (null == vert.tiles) {continue;}
			for (var j in tips) {
				var tip = tips[j];
				if (null == vert.tiles[tip.k]) { continue; }
				e.push([tip.vert, vert]);
			}
		}
		if (2 != e.length) {return;}
		var b = [e[0][0], e[0][1], e[1][1], e[1][0]];

		var a = find_common_neighbors(tips);
		delete a[t.k];
		star = null;
		for (var i in a) { star = a[i]; }
		if (null == star) {return;}
		if (null == star.verts) { find_p1_tiles(star); }
		if (null == star.verts) { return; }

		for (var i in star.verts) {
			var vert = star.verts[i];
			if (null == vert.tiles) {continue;}
			var found = false;
			for (var j in tips) {
				var tip = tips[j];
				found = false;
				if (null == vert.tiles[tip.k]) { break; }
				found = true;
			}
			if (found) {b.push(vert); break;}
		}
		if (5 != b.length) {return;}
		t.verts = b;
		find_p1_tile(t);
	}

	function find_p1_point_verts(t) {
		// two neighbors are tips
		// the other two neighbors are kites
		// the kites share a tip as a neighbor
		// their other neighbors are one of the star types
		var tips = get_neighbors_t(t, 'tip');
		if (2 != tips.length) { return; }
		var kites = get_neighbors_t(t, 'kite');
		if (2 != kites.length) { return; }
		var a = find_common_neighbors(kites);
		delete a[t.k];
		var tip = null;
		for (var i in a) {if ('tip' == a[i].t) { tip = a[i]; break; }}
		if (null == tip) {return;}
		var b = [];
		for (var i in tips) {
			find_tip_vert(tips[i]);
			if (null == tips[i].vert) { return; }
		}

		var sides = [];
		for (var i in kites) {
			var star = null;
			var side = null;
			for (var j in tips) {
				a = find_common_neighbors([kites[i], tips[j]]);
				delete a[t.k];
				for (var k in a) { star = a[k]; }
				if (null == star) { continue; }
				if (null == star.verts) { 
					find_p1_tiles(star); 
				}
				if (null == star.verts) { return; }
				side = [tips[j]];
				break;
			}
			if (null == side) { return; }
			for (var j in star.verts) {
				var vert = star.verts[j];
				if (null == vert.tiles) {continue;}
				if (
					null != vert.tiles[side[0].k] &&
					null != vert.tiles[tip.k]
				) {side.push(vert); break;}
			}
			if (2 != side.length) { return; }
			sides.push(side);
		}
		if (2 != sides.length) { return; }
		var b = [
			tip.vert, 
			sides[0][1], 
			sides[0][0].vert,
			sides[1][0].vert,
			sides[1][1]
		];
		t.verts = b;
		find_p1_tile(t);
	}

	function find_pointed_vert(star, tips) {
		if (null == star.verts) { find_p1_tiles(star); }
		if (null == star.verts) { return null; }
		for (var i in star.verts) {
			var vert = star.verts[i];
			if (null == vert.tiles) {continue;}
			var found = false;
			for (var j in tips) {
				var tip = tips[j];
				found = false;
				if (null == vert.tiles[tip.k]) { break; }
				found = true;
			}
			if (found) {return vert;}
		}
		return null;
	}

	function find_common_other_neighbor(t, a) {
		var o = find_common_neighbors(a);
		delete o[t.k];
		var c = null;
		for (var i in o) { c = o[i]; }
		return c;
	}

	function find_p1_sun_verts(t) {
		// the neighbors are five kites
		// the kites straddle either tips or diamonds
		var kites = get_neighbors_t(t, 'kite');
		if (5 != kites.length) { return; }
		var points = [];
		for (var i = 0; i < 5; ++i) {
			var ot = find_common_other_neighbor(t, [
				kites[i],
				kites[(i + 1) % 5]
			]);
			if (null == ot) { return; }
			points.push(ot);
		}
		var b = [];
		for (var i = 0; i < 5; ++i) {
			var ot = points[i];
			if ('tip' == ot.t) {
				if (null == ot.vert) {
					find_p1_tiles(ot); 
				}
				if (null == ot.vert) { return; }
				b.push(ot.vert);
			} else {
				var vert = find_pointed_vert(points[i], [
					points[(i + 1) % 5],
					points[(i + 6) % 5]
				]);
				if (null == vert) { return; }
				b.push(vert);
			}
		}
		t.verts = b;
		find_p1_tile(t);
	}

	function find_p1_tiles(t) {
		if ('tip' == t.t) {
			find_tip_vert(t);
		} else if (
			'star' == t.t ||
			'boat' == t.t ||
			'diamond' == t.t ||
			false
		) {
			find_big_pentagon_verts(t);
		} else if ('deuce' == t.t) {
			find_p1_deuce_verts(t);
		} else if ('point' == t.t) {
			find_p1_point_verts(t);
		} else if ('sun' == t.t) {
			find_p1_sun_verts(t);
		}
	}

	function task_find_p1_tiles() {
		clearTimeout(task.interval);
		task.queue= new Array();
		for (var i in little_tiles) {
			task.queue.push(little_tiles[i]);
		}
		task.total = task.queue.length;
		task.interval = setTimeout('task_find_next_p1_tiles()', 10);
	}


	function draw_p1_edge(e) {
		if (null != task.edges[e.k]) { return; }
		task.edges[e.k] = true;

		ctx.strokeStyle = 'rgb(15, 15, 15)';
		ctx.lineWidth = 2;
		var a = [];
		for (var i in e.verts) {a.push(e.verts[i]);}
		draw_segments(scale_points(a));
	}

	function draw_tile_edge(e, clr) {
		if (null != task.edges[e.k]) { return; }
		if (! opts['edges']) { return; }
		task.edges[e.k] = true;

		var c = clr;
		if (null == c) { c = "rgb(63, 63, 63)"; }
		if (! opts['colors']) { c = "rgb(127, 127, 127)"; }
		ctx.strokeStyle = c;
		ctx.lineWidth = 1;
		var a = [];
		for (var i in e.verts) {a.push(e.verts[i]);}
		draw_segments(scale_points(a));
	}

	function draw_p1_center(t) {
		var dot = new_draw_dot();
		if (t.vert.t) {
			dot.clr = "gray";
			dot.r = 1;
		} else {
			dot.clr = "white";
			dot.r = 2;
		}
		var p;
		p = scale_points([t.vert])[0];
		dot.x = p.x;
		dot.y = p.y;
		draw_dot(dot);
	}

	function find_little_tile_color(t) {
		if (null != t.color) { return t.color; }
		var a = [];
		for (var i in t.ints) {
			var inter = t.ints[i];
			for (var j in inter.bars) {
				var bar = inter.bars[j];
				a[bar.k] = rainbow_numbers(.5 + bar.d / 12);
			}
		}
		var o = [0, 0, 0];
		var n = 0;
		for (var i in a) {
			++n;
			for (var j in a[i]) {
				o[j] += a[i][j];
			}
		}

		for (var i in o) {
			o[i] = Math.floor(o[i] / n);
		}

		t.color = o;
		return o;
	}

	function draw_thing(t) {
		var f = t.draw;
		if (null == f) { return; }
		else if ('p1_tile' == f) { draw_p1_tile(t); }
		else if ('p3_tile' == f) { draw_p3_tile(t); }
		else if ('p2_tile' == f) { draw_p2_tile(t); }
		else if ('cartwheel' == f) { draw_cartwheel(t); }
	}

	function draw_p3_arcs(t) {
		if (! opts['arcs']) { return; }
		var th = get_theta([t.verts[2], t.verts[0]]);

		th += Math.PI;
		th = - th;

		var p = scale_points([t.verts[0]])[0];
		var d = arc_d;
		var dth = ((t.t ? 2 : 1 ) * Math.PI / 5);

		ctx.lineWidth = 1;
		ctx.strokeStyle = "rgb(31, 31, 31)";

		ctx.save();
		ctx.beginPath();
		ctx.arc(p.x, p.y, d * v.z, th - dth, th + dth, false);
		ctx.stroke();
		ctx.restore();

		d = (t.t ? arc_d : long_d - arc_d);
		th += Math.PI;
		var p = scale_points([t.verts[2]])[0];
		ctx.strokeStyle = "rgb(63, 63, 63)";

		ctx.save();
		ctx.beginPath();
		ctx.arc(p.x, p.y, d * v.z, th - dth, th + dth, false);
		ctx.stroke();
		ctx.restore();

	}

	function fill_tile(t, c) {
		if (! opts['colors']) { return; }
		var m = c;
		if (null == m) { m = 1 / (opts['edges'] ? 16 : 4) ; }

		clr = [0, 0, 0];
		if (null == t.color) { return; }
		for (var i in t.color) {clr[i] = t.color[i];}

		for (var i in clr) { clr[i] = Math.floor(clr[i] * m); }
		clr = 'rgb(' + clr.join(',') + ')';
		ctx.fillStyle = clr;
		fill_polygon(scale_points(t.verts));
	}

	function draw_p3_tile(t) {
		for (var i in t.edges) { draw_tile_edge(t.edges[i], "rgb(31, 31, 31)"); }
		fill_tile(t);
		draw_p3_arcs(t);
	}

	function draw_p2_arcs(t) {
		if (! opts['arcs']) { return; }
		var th = get_theta([t.verts[0], t.verts[2]]);
			th += Math.PI;
			th = - th;
			th -= Math.PI

		var m;
		d = long_d;
		if ('kite' == t.t) {
			m = 2;
			d = d / phi;
		} else {
			m = 3;
			d = d * (1 - 1/phi);
		}


		var dth = Math.PI / 5;

		p = scale_points([t.verts[0]])[0];

		ctx.strokeStyle = "gray";
		ctx.lineWidth = 1;
		ctx.save();
		ctx.beginPath();
		ctx.arc(p.x, p.y, d * v.z, th - dth, th + dth, false);
		ctx.stroke();

		p = scale_points([t.verts[2]])[0];
		th += Math.PI;
		dth = m * Math.PI / 5;
		d /= phi;

		ctx.strokeStyle = "white";
		ctx.lineWidth = 1;
		ctx.save();
		ctx.beginPath();
		ctx.arc(p.x, p.y, d * v.z, th - dth, th + dth, false);
		ctx.stroke();


	}

	function draw_p2_tile(t) {
		for (var i in t.edges) { draw_tile_edge(t.edges[i]); }
		fill_tile(t);
		draw_p2_arcs(t);
	}

	function fix_cartwheel(t) {
		t.color = t.p1_tile.color;
		var b = get_neighbors_t(t.p1_tile, 'deuce');
		var a = [];
		for (var i in b) { a[b[i].k] = b[i]; }
		var verts = [];
		for (var i in a) { 
			for (var j in a[i].verts) {
				var vert = a[i].verts[j];

				if (null != verts[vert.k]) {
					t.vert = vert;
					break;
				}
				verts[vert.k] = vert;
			}
			if (null != t.vert) { break; }
		}
		if (null == t.vert) { return; }
		for (var i in a) {
			b = get_neighbors_t(a[i], 'star');
			if (b.length > 0) {
				t.bottom = b[0].vert;
				break;
			}
		}
		if (null == t.bottom) { return; }
		var th = get_theta([t.vert, t.bottom]);
		var r = get_distance(t.vert, t.bottom);
		t.verts = get_regular_polygon_points(10, t.vert.x, t.vert.y, r, th);
	}

	function draw_cartwheel(t) {
		if (null == t.vert) { fix_cartwheel(t); }
		if (null == t.bottom) { return; }

		var clr = [];
		for (var i in t.color) {clr[i] = t.color[i];}
		var dot = new_draw_dot();
		dot.clr = "rgb(" + clr.join(',') + ")";
		dot.r = 2;

		var p = scale_points([t.vert])[0];
		dot.x = p.x;
		dot.y = p.y;
		draw_dot(dot);

		if (null == t.bottom) { return; }

		ctx.lineWidth = 3;
		for (var i in clr) {clr[i] = Math.floor(clr[i] / 2);}
		ctx.strokeStyle = "rgb(" + clr.join(',') + ")";
		draw_polygon(scale_points(t.verts));

		ctx.lineWidth = 2;
		for (var i in clr) {clr[i] = Math.floor(clr[i] / 2);}
		ctx.strokeStyle = "rgb(" + clr.join(',') + ")";
		draw_segments(scale_points([t.vert, t.bottom]));

		fill_tile(t);
	}

	function draw_p1_tile(t) {
		if (null == t.verts) {return;}
		for (var i in t.edges) { 
			draw_tile_edge(t.edges[i], 'rgb(15,15,15)' ); 
		}

		fill_tile(t);
		//draw_p1_center(t);
	}

	function find_p3_vert(vert) {
		var k = vert.k;
		var o = p3_verts[vert.k];
		if (null == o) {
			o = {k: vert.k};
			o.x = vert.x;
			o.y = vert.y;
			o.t = vert.t;
			o.color = vert.color;
			o.p1_tile = vert.tile;
			o.tiles = [];
			o.edges = [];
			p3_verts[o.k] = o;
		}
		return o;
	}

	function find_p3_edge(a, b) {
		if (null == a || null == b) {return;}
		var k = [a.k, b.k];
		k.sort();
		k = "p3edge:" + k.join("x");
		if (null == p3_edges[k]) {
			p3_edges[k] = {k: k, tiles: []};
			var verts = [];
			verts[a.k] = a;
			verts[b.k] = b;
			for (var i in verts) {
				verts[i] = find_p3_vert(verts[i]);
				verts[i].edges[k] = p3_edges[k];
				p3_edges[k].verts = verts;
			}
			/*
			ctx.strokeStyle = "rgb(0,127,127)";
			ctx.lineWidth = 1;
			draw_segments(scale_points([a, b]));
			*/
		}
		return p3_edges[k];
	}

	function find_p3_edges(t) {
		var n = [];
		var d = [];
		for (var i in t.edges) {
			var ot = find_neighbor_tile(t, t.edges[i]);
			if (null == ot) { continue; }
			if (null != n[ot.k]) { d[ot.k] = ot; }
			n[ot.k] = ot;
		}
		if ( t.vert.t ) {
			for (var i in n) {
				if (null == d[i] && 'deuce' == n[i].t) { 
					delete n[i]; 
				}
			}
		}
		if ('deuce' == t.t) {
			for (var i in n) {
				if (null == d[i] && n[i].vert.t) { 
					delete n[i]; 
				}
			}
		}

		if ('diamond' == t.t) {
			var a = get_neighbors_t(t, 'deuce');
			if (2 == a.length) {
				var ot = find_common_other_neighbor(t, a);
				if (null != ot) { n.push(ot); }
			}
		}

		for (var i in n) {
			find_p3_edge(t.vert, n[i].vert);
		}
	}

	function find_p3_type(t) {
		// is this a wide or a narrow p3 tile?
		/*
		if this is a narrow tile, the root vertex is at the center
		of a p1 boat or diamond and the tip is in a p1 deuce

		*/
		var root = t.verts[0];
		var tip = t.verts[2];
		t.t = (
			('boat' == t.verts[0].p1_tile.t ||
			'diamond' == t.verts[0].p1_tile.t ) && 
			'deuce' == t.verts[2].p1_tile.t);

		if (t.t && null == arc_d) {
			arc_d = get_distance(t.verts[0], t.verts[2]) / 2;
		}
	}

	function find_p3_dart_vert(t) {
		if (t.t) { return; }
		if (null == long_d) {
			long_d = get_distance(t.verts[0], t.verts[1]);
		}
		t.dart_vert = get_midpoint_d(long_d, t.verts[2], t.verts[0]);
		t.dart_vert.k = t.dart_vert.x + ',' + t.dart_vert.y;
		t.dart_vert.t = true;

		/*
		var dot = new_draw_dot();
		dot.clr = "gray";
		dot.r = 1;

		var p = scale_points([t.dart_vert])[0];
		dot.x = p.x;
		dot.y = p.y;
		draw_dot(dot);
		*/
	}

	function find_tile_color(t) {
		var clr = [0, 0, 0];
		var n = 0;
		for (var i in t.verts) {
			if (null == t.verts[i].color) { continue; }
			++n;
			for (var j in t.verts[i].color) {
				clr[j] += t.verts[i].color[j];
			}
		}
		if (n > 0) {
			for (var i in clr) {
				clr[i] = Math.floor(clr[i] / n);
			}
		}
		t.color = clr;
	}

	function find_p3_tile(v0, v1, v2, v3) {
		var k = [v0.k, v2.k];
		k = 'p3:[' + k.join('][') + ']';
		if (null == p3_tiles[k]) {
			var o = {k: k, verts: [v0, v1, v2, v3], edges: []}
			o.draw = 'p3_tile';
			for (var i = 0; i < 4; ++i) {
				var va = o.verts[i];
				var vb = o.verts[(i + 1) % 4];
				var e = find_p3_edge(va, vb);
				if (null == e) { return null; }
				e.tiles[k] = o;
				o.edges[e.k] = e;
			}
			find_p3_type(o);
			find_p3_dart_vert(o);
			find_tile_color(o);
			p3_tiles[k] = o;
			/*
			ctx.strokeStyle = "rgb(31,31,31)";
			//draw_polygon(scale_points(o.verts));
			c = (o.t ? "rgb(31,15,0)" : "rgb(0,15,31)");
			ctx.fillStyle = c;
			fill_polygon(scale_points(o.verts));
			*/
		}
		return p3_tiles[k];
	}

	function find_p3_tiles(vert) {
		//find all p3 tiles surrounding this vert.
		// follow all edges surrounding this vert out two edges
		// all pairs of paths that have a common vert at the end
		// form a tile

		if (! vert.t) {return;}
		task.found[vert.k] = true;
		var t = [];
		for (var i0 in vert.edges) {
			var e0 = vert.edges[i0];
			for (var j0 in e0.verts) {
				var v1 = e0.verts[j0];
				if (task.found[v1.k]) { continue; }
				for (var i1 in v1.edges) {
					var e1 = v1.edges[i1];
					if (e1.k == e0.k) { continue; }
					for (var j1 in e1.verts) {
						var v2 = e1.verts[j1];
						if (v2.k == v1.k) { continue; }
						if (task.found[v2.k]) { continue; }
						if (null == t[v2.k]) { 
							t[v2.k] = []; 
						}
						t[v2.k][v1.k] = [v1, v2]; 
					}
				}
			}
		}

		for (var i in t) {
			var a = [];
			for (var j in t[i]) {
				a.push(t[i][j]);
			}
			if (2 != a.length) { continue; }
			find_p3_tile(vert, a[0][0], a[0][1], a[1][0]);
		}
	}

	function find_p2_edge(a, b) {
		var k = [a.k, b.k];
		k.sort();
		k = 'p2edge:' + k.join('x');

		var o = p2_edges[k];
		if (null == o) {
			o = {k: k, verts: [], tiles: []};

			o.verts[a.k] = a;
			a.edges[o.k] = o;

			o.verts[b.k] = b;
			b.edges[o.k] = o;

			p2_edges[k] = o;

			/*
			ctx.lineWidth = 3;
			ctx.strokeStyle = "rgb(63, 63, 0)";
			draw_segments(scale_points([a, b]));
			*/
		}
		return o;
	}

	function find_p2_edges(t) {
		for (var i = 0; i < 4; ++i) {
			var e = find_p2_edge(t.verts[i], t.verts[(i + 1) % 4]);
			t.edges[e.k] = e;
			e.tiles[t.k] = t;
			e.t = (i == 1 || i == 2);
		}
	}

	function find_p2_vert(vert) {
		var o = p2_verts[vert.k];
		if (null == o) {
			o = {
				k: vert.k, 
				x: vert.x, 
				y: vert.y, 
				color: vert.color,
				t: vert.t, 
				edges: [],
				tiles: []
			};
			p2_verts[vert.k] = o;
		}
		return o;
	}

	function find_p2_verts(t, a) {
		for (var i = 0; i < a.length; ++i) {
			var vert = find_p2_vert(a[i]);
			t.verts.push(vert);
			vert.tiles[t.k] = t;
		}	
	}

	function find_p2_tile(t, a) {
		var k = [];

		for (i in a) {k.push(a[i].k);}
		k.sort();
		k = t + ':' + k.join(':');
		var o = p2_tiles[k];
		if (null == o) {
			o = { k: k, edges: [], verts: [], t: t};
			o.draw = 'p2_tile';
			find_p2_verts(o, a);
			find_p2_edges(o);
			find_tile_color(o);
			p2_tiles[k] = o;

			/*
			ctx.fillStyle = (
				('dart' == t) ? 
					"rgb(31, 0, 0)" : 
					"rgb(0, 31, 0)" 
			); 
			fill_polygon(scale_points(a));
			*/
		}
		return o;
	}

	function find_p2_tiles(t) {
		// every wide p3 tile maps to a dart and two half-kites

		// we won't bother with narrow tiles, since they just
		// contain the other half of kites from wide tiles

		if (t.t) { return; }

		// we can find the dart first because it's easy

		find_p2_tile("dart", [t.verts[0], t.verts[1], t.dart_vert, t.verts[3]]);


		var ot = find_neighbor_tile(t, find_p3_edge(t.verts[2], t.verts[1]));
		if (null != ot) { 
			var ov = ot.t ? ot.verts[0] : ot.dart_vert;
			find_p2_tile("kite", [t.verts[2], t.dart_vert, t.verts[1], ov]);
		}

		ot = find_neighbor_tile(t, find_p3_edge(t.verts[2], t.verts[3]));
		if (null != ot) { 
			var ov = ot.t ? ot.verts[0] : ot.dart_vert;
			find_p2_tile("kite", [t.verts[2], t.dart_vert, t.verts[3], ov]);
		}

	}

	function task_find_next_p2_tiles() {

		clearTimeout(task.interval);
		var a = new Array();
		for (var i = 0; i < 32; ++i) {
			if (task.queue.length < 1) { break; }
			a.push(task.queue.shift());
		}
		for (var i in a) { 
			find_p2_tiles(a[i]); 
		}
	       	status_update("2t" + Math.floor(100 * (task.total - task.queue.length) / task.total) + '%');
		if (a.length > 0) {
			task.interval = setTimeout('task_find_next_p2_tiles()', 10);
		} else {
			task.interval = setTimeout('task_ready()', 10);
		}
	}

	function task_find_p2_tiles() {
		clearTimeout(task.interval);
		task.queue = new Array();
		for (var i in p3_tiles) {
			task.queue.push(p3_tiles[i]);
		}
		task.total = task.queue.length;
		task.found = [];
		task.interval = setTimeout('task_find_next_p2_tiles()', 10);
	}

	function task_find_next_p3_tiles() {

		clearTimeout(task.interval);
		var a = new Array();
		for (var i = 0; i < 32; ++i) {
			if (task.queue.length < 1) { break; }
			a.push(task.queue.shift());
		}
		for (var i in a) { 
			find_p3_tiles(a[i]); 
		}
	       	status_update("3t" + Math.floor(100 * (task.total - task.queue.length) / task.total) + '%');
		if (a.length > 0) {
			task.interval = setTimeout('task_find_next_p3_tiles()', 10);
		} else {
			task.interval = setTimeout('task_find_p2_tiles()', 10);
		}
	}

	function task_find_p3_tiles() {
		clearTimeout(task.interval);
		task.queue = new Array();
		for (var i in p3_verts) {
			task.queue.push(p3_verts[i]);
		}
		task.total = task.queue.length;
		task.found = [];
		task.interval = setTimeout('task_find_next_p3_tiles()', 10);
	}

	function task_find_next_p3_edges() {

		clearTimeout(task.interval);
		var a = new Array();
		for (var i = 0; i < 32; ++i) {
			if (task.queue.length < 1) { break; }
			a.push(task.queue.shift());
		}
		for (var i in a) { 
			find_p3_edges(a[i]); 
		}
	       	status_update("3e" + Math.floor(100 * (task.total - task.queue.length) / task.total) + '%');
		if (a.length > 0) {
			task.interval = setTimeout('task_find_next_p3_edges()', 10);
		} else {
			task.interval = setTimeout('task_find_p3_tiles()', 10);
		}
	}

	function task_find_p3_edges() {
		clearTimeout(task.interval);
		task.queue = new Array();
		for (var i in p1_tiles) {
			task.queue.push(p1_tiles[i]);
		}
		task.total = task.queue.length;
		task.interval = setTimeout('task_find_next_p3_edges()', 10);
	}


	function task_draw_next_tiles() {

		clearTimeout(task.interval);
		var a = new Array();
		for (var i = 0; i < 32; ++i) {
			if (task.queue.length < 1) { break; }
			a.push(task.queue.shift());
		}
		for (var i in a) { 
			draw_thing(a[i]); 
		}
	       	status_update("d" + Math.floor(100 * (task.total - task.queue.length) / task.total) + '%');
		if (a.length > 0) {
			task.interval = setTimeout('task_draw_next_tiles()', 10);
		} else {
			status_update("ready");
			//task.interval = setTimeout('task_ready()', 10);
		}
	}

	function task_draw_tiles() {
		clearTimeout(task.interval);
		task.queue = new Array();
		task.edges =  new Array();
		var a = [];
		find_options();
		if (opts['bars']) {draw_all_bars();}
		var t = [];
		if (opts['p1']) {t.push(p1_tiles);}
		if (opts['p3']) {t.push(p3_tiles);}
		if (opts['p2']) {t.push(p2_tiles);}
		if (opts['cartwheel']) {t.push(cartwheels);}

		for (var i in t) { for (var j in t[i]) { a.push(t[i][j]); } }

		for (var i in a) {
			//task.queue.push(a[i]);
			task.queue.unshift(a[i]);
			var p = Math.floor(task.queue.length * Math.random());
			var t = task.queue[p];
			task.queue[p] = task.queue[0];
			task.queue[0] = t;
		}
		task.total = task.queue.length;
		task.interval = setTimeout('task_draw_next_tiles()', 10);
	}


	function task_ready() {
		clearTimeout(task.interval);
		task.ready = true;
		animate_tiles();
	}

	function find_uncommon_bars(a) {
		var o = new Array();
		var n = 0;
		for (var i in a) {
			++n;
			for (var j in a[i].bars) {
				var b = a[i].bars[j]
				if (null == o[b.k]) { o[b.k] = 0; }
				++o[b.k];
			}
		}
		var c = new Array();
		for (var k in o) {
			if (o[k] < n) { c.push(bars[k]); }
		}
		return c;
	}

	function task_finish_drawing() {
		clearTimeout(task.interval);

		task.ready = true;
		animate_tiles();
	}

	function redraw() {
		console_clear();
		clear_tasks();
		canvas_clear();
		clear_globals();
		ctx.strokeStyle = "white";

		task.interval = setTimeout('task_find_bars()', 10);


	}

	function view_scroll(x, y) {
		//how big is the screen?
		var hx = canvas.width / v.z;
		var hy = canvas.height / v.z;
		var scr = hx;
		if (hy < hx) { scr = hy; }
		scr /= phi;
		set_viewport(v.x + x * scr, v.y + y * scr, v.z);
	}

	function view_zoom(z) {
		set_viewport(v.x, v.y, v.z * Math.pow(phi, z));
	}

	function set_viewport(x, y, z) {
		status_clear();
		v.x = x;
		v.y = y;
		v.z = z;

		// what are the edges of the viewport ?
		// z is pixels/unit
		var w = phi + canvas.width / v.z;
		var h = phi + canvas.height / v.z;

		v.l = [{x: v.x - w, y: v.y - h}, {x: v.x - w, y: v.y + h}];
		v.t = [{x: v.x - w, y: v.y - h}, {x: v.x + w, y: v.y - h}];
		v.r = [{x: v.x + w, y: v.y - h}, {x: v.x + w, y: v.y + h}];
		v.b = [{x: v.x - w, y: v.y + h}, {x: v.x + w, y: v.y + h}];

		v.d = new Object;
		v.d.w = w;
		v.d.h = h;
		v.d.l = v.x - v.d.w;
		v.d.t = v.y - v.d.h;
		v.d.r = v.x + v.d.w;
		v.d.b = v.y + v.d.h;

		v.cx = canvas.width / 2;
		v.cy = canvas.height / 2;

		redraw();
	}

	function console_clear() {
		var c = document.getElementById("console");
		c.innerHTML = '';
	}

	function status_clear() {
		var c = document.getElementById("status");
		c.innerHTML = '';
	}

	function status_update(s) {
		var c = document.getElementById("status");
		var o = "" + s;
		o.replace(/&/, '&amp;');
		o.replace(/</, '&lt;');
		o.replace(/>/, '&gt;');
		o.replace(/'/, '&apos;');
		o.replace(/"/, '&quot;');
		c.innerHTML = o;
	}

	function console_log(s) {
		var c = document.getElementById("console");
		var o = "" + s;
		o.replace(/&/, '&amp;');
		o.replace(/</, '&lt;');
		o.replace(/>/, '&gt;');
		o.replace(/'/, '&apos;');
		o.replace(/"/, '&quot;');
		c.innerHTML += o + '<br />';
	}

	var middle_c;

	function find_musical_distance(t) {
		var c = (phi + 1) / 2;
		if (0 == t) { return 1 - c; }
		var m = ((t < 0) ? -1 : 1);
		var i = Math.abs(t) - 2;
		middle_c_term(i + 3);
		var d = c * m;
		if (i < 0) { return d; }
		d += m * (middle_c.S[i] + middle_c.L[i] * phi);
		return d;
	}

	function expand_middle_c() {
		// l, s l, l s l, s l l s l,
		if (0 == middle_c.m.length) {
			middle_c.m = ['L', 'S', 'L'];
			middle_c.L = [1, 1, 2];
			middle_c.S = [0, 1, 1];
			middle_c.a = 0;
			middle_c.b = 1;
			return;
		}
		var b = middle_c.m.length;
		var i;
		var t;
		for (i = middle_c.a; i < b; ++i) {
			t = middle_c.m[i];
			middle_c.m.push(t);
			middle_c.L.push(middle_c.L[middle_c.L.length - 1]);
			middle_c.S.push(middle_c.S[middle_c.S.length - 1]);
			++middle_c[t][middle_c[t].length - 1];
		}
		middle_c.a = middle_c.b;
		middle_c.b = b;
	}

	function middle_c_term(n) {
		var t = n;
		if (0 == t) { return 'S'; }
		if (1 == t) { return 'L'; }
		if (t > 1) { t -= 2; }
		else { ++t; t *= -1; }
		if (null == middle_c) { middle_c = {
			m: [],
			L: [],
			S: [],
			a: 0,
			b: 0
		}}
		while (middle_c.m.length < t) {
			expand_middle_c();
		}
		return middle_c.m[t];
	}

	function find_option(s) {
		var o = document.getElementById(s);
		if (null == o) {return};
		opts[s] = (true == o.checked);
	}

	function find_options() {
		opts = new Object();
		find_option('p1');
		find_option('p2');
		find_option('p3');
		find_option('cartwheel');
		find_option('bars');
		find_option('arcs');
		find_option('edges');
		find_option('colors');
	}

</script>
</head>
<body onload="init()">
	<canvas id="canvas" class="canvas"  width="320" height="280"></canvas>
		<form action="javascript:" method="get">
			<input type="button" value="&lt" 
				onClick="view_scroll(-1, 0)"/>
			<input type="button" value="&gt" 
				onClick="view_scroll(1, 0)"/>
			<input type="button" value="/\" 
				onClick="view_scroll(0, -1)"/>
			<input type="button" value="\/" 
				onClick="view_scroll(0, 1)"/>
			<input type="button" value="+"
				onClick="view_zoom(1)"/>
			<input type="button" value="-"
				onClick="view_zoom(-1)"/>
			<br />
			<input type="checkbox" id="bars" checked="checked" onClick="animate_tiles()" /> 
			<input type="checkbox" id="p1" checked="checked" onClick="animate_tiles()" /> 
			<input type="checkbox" id="p3" checked="checked" onClick="animate_tiles()" /> 
			<input type="checkbox" id="p2" checked="checked" onClick="animate_tiles()" /> 
			<input type="checkbox" id="cartwheel" checked="checked" onClick="animate_tiles()" /> 
			<input type="checkbox" id="arcs" checked="checked" onClick="animate_tiles()" /> 
			<input type="checkbox" id="edges" checked="checked" onClick="animate_tiles()" /> 
			<input type="checkbox" id="colors" checked="checked" onClick="animate_tiles()" /> 
			<!--
			<input type="checkbox" id="arc_a" checked="checked" onClick="animate_tiles()" /> 
			<input type="checkbox" id="arc_b" checked="checked" onClick="animate_tiles()" /> 
			<input type="checkbox" id="b_long" checked="checked" onClick="animate_tiles()" /> 
			<input type="checkbox" id="b_short" checked="checked" onClick="animate_tiles()" /> 
			<input type="checkbox" id="tile" checked="checked" onClick="animate_tiles()" /> 
			<input type="checkbox" id="ints" checked="checked" onClick="animate_tiles()" /> 
			<input type="checkbox" id="vert_a" checked="checked" onClick="animate_tiles()" /> 
			<input type="checkbox" id="vert_b" checked="checked" onClick="animate_tiles()" /> 
			-->
			<br />
			<span id="status"></span>
		</form>
		<!--<div id="message"></div>-->
	<div id="console"></div>
</body>
</html>
